﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace Aeon.Emulator
{
    /// <summary>
    /// Contains information about conventional memory usage.
    /// </summary>
    public sealed class ConventionalMemoryInfo
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the ConventionalMemoryInfo class.
        /// </summary>
        /// <param name="processes">Collection of process allocations.</param>
        /// <param name="largestFreeBlock">Size of the largest free block of contiguous conventional memory.</param>
        internal ConventionalMemoryInfo(IEnumerable<ProcessAllocation> processes, int largestFreeBlock)
        {
            this.processes = new List<ProcessAllocation>(processes).AsReadOnly();
            this.totalUsed = (from p in this.processes
                              select p.AllocationSize).Sum();
            this.largestFreeBlock = largestFreeBlock;
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the collection of process allocations.
        /// </summary>
        public IEnumerable<ProcessAllocation> Processes
        {
            get { return processes; }
        }
        /// <summary>
        /// Gets the number of conventional memory bytes allocated.
        /// </summary>
        public int MemoryUsed
        {
            get { return totalUsed; }
        }
        /// <summary>
        /// Gets the number of available conventional memory bytes.
        /// </summary>
        public int MemoryFree
        {
            get { return ConventionalMemorySize - totalUsed; }
        }
        /// <summary>
        /// Gets the size of the largest free block of contiguous conventional memory.
        /// </summary>
        public int LargestFreeBlock
        {
            get { return largestFreeBlock; }
        }
        #endregion

        #region Private Fields
        private readonly ReadOnlyCollection<ProcessAllocation> processes;
        private readonly int totalUsed;
        private readonly int largestFreeBlock;
        #endregion

        #region Public Constants
        /// <summary>
        /// Size of conventional memory in bytes.
        /// </summary>
        public const int ConventionalMemorySize = 0xA0000;
        #endregion
    }

    /// <summary>
    /// Contains information about an allocation in conventional memory.
    /// </summary>
    public struct ProcessAllocation : IEquatable<ProcessAllocation>
    {
        #region Constructors
        /// <summary>
        /// Initializes a new ProcessAllocation struct.
        /// </summary>
        /// <param name="processName">Name of the process which owns the allocation.</param>
        /// <param name="size">Size of the allocation in bytes.</param>
        public ProcessAllocation(string processName, int size)
        {
            this.processName = processName;
            this.size = size;
        }
        #endregion

        #region Operators
        /// <summary>
        /// Tests for equality between two ProcessAllocation structs.
        /// </summary>
        /// <param name="value1">First value.</param>
        /// <param name="value2">Second value.</param>
        /// <returns>True if structs are equal; otherwise false.</returns>
        public static bool operator ==(ProcessAllocation value1, ProcessAllocation value2)
        {
            return value1.Equals(value2);
        }
        /// <summary>
        /// Tests for inequality between two ProcessAllocation structs.
        /// </summary>
        /// <param name="value1">First value.</param>
        /// <param name="value2">Second value.</param>
        /// <returns>True if structs are not equal; otherwise false.</returns>
        public static bool operator !=(ProcessAllocation value1, ProcessAllocation value2)
        {
            return !value1.Equals(value2);
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the name of the process which owns the allocation.
        /// </summary>
        public string ProcessName
        {
            get { return processName; }
        }
        /// <summary>
        /// Gets the size of the allocation in bytes.
        /// </summary>
        public int AllocationSize
        {
            get { return size; }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Returns a string representation of the ProcessAllocation struct.
        /// </summary>
        /// <returns>String representation of the ProcessAllocation struct.</returns>
        public override string ToString()
        {
            return string.Format("{0}: {1}", this.ProcessName, this.AllocationSize);
        }
        /// <summary>
        /// Returns a code used for hashing algorithms.
        /// </summary>
        /// <returns>Hash code for the struct.</returns>
        public override int GetHashCode()
        {
            if(string.IsNullOrEmpty(processName))
                return size.GetHashCode();
            else
                return processName.GetHashCode() ^ size.GetHashCode();
        }
        /// <summary>
        /// Tests for equality with another object.
        /// </summary>
        /// <param name="obj">Other object to test.</param>
        /// <returns>True if objects are equal; otherwise false.</returns>
        public override bool Equals(object obj)
        {
            if(obj is ProcessAllocation)
                return Equals((ProcessAllocation)obj);
            else
                return false;
        }
        /// <summary>
        /// Tests for equality with another ProcessAllocation struct.
        /// </summary>
        /// <param name="other">Other ProcessAllocation struct to test.</param>
        /// <returns>True if structs are equal; otherwise false.</returns>
        public bool Equals(ProcessAllocation other)
        {
            return this.ProcessName == other.ProcessName && this.AllocationSize == other.AllocationSize;
        }
        #endregion

        #region Private Fields
        private string processName;
        private int size;
        #endregion
    }
}
